#include "cybertron/core/UtilSharedMemory.hpp"
#include "cybertron/core/Log.hpp"

#ifdef CYBERTRON_WIN
#include "cybertron/core/UtilString.hpp"
#endif
CYBERTRON_BEGIN
#define SM_Mutex_TAIL_FIX "_SM_Mutex"
#ifdef CYBERTRON_WIN
SharedMemoryReceiver::SharedMemoryReceiver(const std::string& label, int size)
{
    mMapFile.label = label;
    mMapFile.size = size;
    OpenFileMap();
}

SharedMemoryReceiver::~SharedMemoryReceiver()
{
    if (mMapFile.handle) {
        UnmapViewOfFile(mMapFile.lp);
        CloseHandle(mMapFile.handle);
    }
}

void SharedMemoryReceiver::OpenFileMap()
{
    mMapFile.handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, (LPCSTR)(mMapFile.label.c_str()));
    if (mMapFile.handle) {
        mMapFile.lp = MapViewOfFile(mMapFile.handle, FILE_MAP_ALL_ACCESS, 0, 0, mMapFile.size);
        if (!mMapFile.lp)
        {
            CloseHandle(mMapFile.handle);
            mMapFile.handle = nullptr;
        }
    }
}

bool SharedMemoryReceiver::Receive(char* data)
{
    if (!mMapFile.handle) {
        OpenFileMap();
    }

    if (mMapFile.handle) {
        memcpy(data, mMapFile.lp, mMapFile.size);
        return true;
    }

    return false;
}

SharedMemorySender::SharedMemorySender(const std::string& label, int size)
{
    mMapFile.handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, (LPCSTR)(label.c_str()));
    if (mMapFile.handle) {
        mMapFile.lp = MapViewOfFile(mMapFile.handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
        if (!mMapFile.lp)
        {
            CloseHandle(mMapFile.handle);
            mMapFile.handle = nullptr;
        }
    }
    mMapFile.size = size;
}

SharedMemorySender::~SharedMemorySender()
{
    if (mMapFile.handle) {
        UnmapViewOfFile(mMapFile.lp);
        CloseHandle(mMapFile.handle);
    }
}

bool SharedMemorySender::Send(const char* data)
{
    if (mMapFile.handle) {
        memcpy(mMapFile.lp, data, mMapFile.size);
        return true;
    }

    return false;
}
#endif


bool SharedMemory::sharedMemoryCreateImpl(std::string label, int size, SharedMemoryHandle& handle)
{
	handle.label = label;
	handle.size = size;
#ifdef CYBERTRON_WIN
	HANDLE hMutex = CreateMutex(NULL, FALSE, UtilString::u2w(std::string(label).append(SM_Mutex_TAIL_FIX)).c_str());
	if (!hMutex)
	{
		logError("SharedMemoryCreate CreateMutex failed: GetLastError()=%d", GetLastError());
		return false;
	}
	handle.handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, UtilString::u2w(label).c_str());
	if (!handle.handle)
		return false;

	handle.lp = MapViewOfFile(handle.handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
	if (!handle.lp)
	{
		CloseHandle(handle.handle);
		return false;
	}
#elif CYBERTRON_LINUX
	std::string sem_name = label;
	sem_name.append(SM_Mutex_TAIL_FIX);
	sem_t* mutex   = sem_open(sem_name.c_str(), O_CREAT, 0644, 1);
	sem_init(mutex,1,1);
	if(mutex == SEM_FAILED)
	{
		logError("sem_open failed in create sharememory");
		return false;
	}
	int fd = shm_open(label.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	if (fd < 0)
		return false;
    close(fd);
    handle.fd = -1; // don't set the fd handle here for accident close
#endif
	return true;
}

void SharedMemory::sharedMemoryDestroyImpl(SharedMemoryHandle& handle)
{
#ifdef CYBERTRON_WIN
	UnmapViewOfFile(handle.lp);
	CloseHandle(handle.handle);
#elif CYBERTRON_LINUX
	shm_unlink(handle.label.c_str());
	std::string sem_name = handle.label;
	sem_name.append(SM_Mutex_TAIL_FIX);
	sem_unlink(sem_name.c_str());
#endif
}

bool SharedMemory::sharedMemoryWriteImpl(std::string label, int size, const char* data)
{
#ifdef CYBERTRON_WIN
	HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UtilString::u2w(std::string(label).append(SM_Mutex_TAIL_FIX)).c_str());
	if (!hMutex)
	{
		logError("SharedMemoryWrite OpenMutex failed: GetLastError()=%d", GetLastError());
		return false;
	}
	WaitForSingleObject(hMutex, INFINITE);

	HANDLE handle = OpenFileMapping(FILE_MAP_WRITE, NULL, UtilString::u2w(label).c_str());
	if (!handle)
		return false;
	LPVOID lp = MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, 0);
	memcpy(lp, data, size);
	UnmapViewOfFile(lp);
	CloseHandle(handle);

	ReleaseMutex(hMutex);
	return true;

#elif CYBERTRON_LINUX
	std::string sem_name = label;
	sem_name.append(SM_Mutex_TAIL_FIX);
	bool bSucceed = false;
	sem_t* mutex  = sem_open(sem_name.c_str(), O_EXCL);
	if (mutex != SEM_FAILED)
	{
		sem_wait(mutex);
		int fd = shm_open(label.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
		if (fd >= 0 && ftruncate(fd, size) >= 0){
			void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			if (p && p != MAP_FAILED){
				memcpy(p, data, size);
				munmap(p, size);
				bSucceed = true;
			}
			close(fd);
		}
		sem_post(mutex);
		sem_close(mutex);
	}
	return bSucceed;
	
#endif 
}

bool SharedMemory::sharedMemoryReadImpl(std::string label, int size, char* data)
{
#ifdef CYBERTRON_WIN
	HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UtilString::u2w(std::string(label).append(SM_Mutex_TAIL_FIX)).c_str());
	if (!hMutex)
	{
		logError("SharedMemoryRead OpenMutex failed: GetLastError()=%d", GetLastError());
		return false;
	}
	WaitForSingleObject(hMutex, INFINITE);
	HANDLE handle = OpenFileMapping(FILE_MAP_READ, NULL, UtilString::u2w(label).c_str());
	if (!handle)
		return false;
	LPVOID lp = MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0);
	memcpy(data, lp, size);
	UnmapViewOfFile(lp);
	CloseHandle(handle);
	ReleaseMutex(hMutex);
	return true;
	
#elif CYBERTRON_LINUX
	std::string sem_name = label;
	sem_name.append(SM_Mutex_TAIL_FIX);
	bool bSucceed = false;
	sem_t* mutex  = sem_open(sem_name.c_str(), O_EXCL);
	if (mutex != SEM_FAILED)
	{
		sem_wait(mutex);
		int fd = shm_open(label.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
		if (fd >= 0 && ftruncate(fd, size) >= 0){
			void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			if (p && p != MAP_FAILED){
				memcpy(data, p, size);
				munmap(p, size);
				bSucceed = true;
			}
			close(fd);
		}
		sem_post(mutex);
		sem_close(mutex);
	}
	return bSucceed;

#endif
}

bool SharedMemory::sharedMemoryExistImpl(std::string label)
{
#ifdef CYBERTRON_WIN
	HANDLE handle = OpenFileMapping(FILE_MAP_READ, NULL, UtilString::u2w(label).c_str());
	if (!handle)
		return false;
	CloseHandle(handle);
#elif CYBERTRON_LINUX
	int fd = shm_open(label.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
	if (fd < 0)
		return false;
	close(fd);
#endif
	return true;
}
CYBERTRON_END
